//
//  Author:
//    savio saviosun@gmail.com
//
//  Copyright (c) 2017, savio
//
//  All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
//       the documentation and/or other materials provided with the distribution.
//     * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

using GF.Debug;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

namespace SA.Common
{
    public enum eFileOperation
    {
        E_ReadFile,
        E_WriteFile,
        E_DeleteFile,
        E_CreateDirectory,
        E_DeleteDirectory
    }

    public class FileSystem
    {
        public delegate void DelegateOnOperateFileFail(string fullPath, eFileOperation fileOperation);

        private static string s_cachePath = null;

        public static string s_ifsExtractFolder = "Resources";
        public static string s_ifsExtractPath = Application.dataPath + "/Resources";
        public static string s_localPathUrlHeader = "file://";

        private static readonly MD5CryptoServiceProvider s_md5Provider = new MD5CryptoServiceProvider();

        public static DelegateOnOperateFileFail s_delegateOnOperateFileFail = delegate
        {
        };

        //security code
        private static readonly byte sFactor = 40;
        private static readonly int sLFactor = 3;
        private static readonly int sRFactor = 5;
        private static readonly byte[] sFactorList = new byte[]
        {
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
            0x8A, 0x7B, 0x6C, 0x5D, 0x4E, 0x3F, 0xDE, 0xAD
        };

        static public byte Encode(byte inbyte)
        {
            inbyte = (byte)(0xff & ((inbyte >> sLFactor) | (inbyte << sRFactor)));
            return (byte)(inbyte ^ sFactor);
        }

        static public byte Decode(byte inbyte)
        {
            inbyte ^= sFactor;
            return (byte)(0xff & ((inbyte << sLFactor) | (inbyte >> sRFactor)));
        }

        static public void EasyCrypt(ref byte[] indata)
        {
            for (int ii = 0; ii < indata.Length; ++ii)
                indata[ii] ^= sFactorList[ii % sFactorList.Length];
        }

        public static byte[] AESEncrypt(byte[] rawBytes, string Key, string IV)
        {
            // Check arguments.
            if (rawBytes == null || rawBytes.Length <= 0)
                throw new ArgumentNullException("RawBytes");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            byte[] encrypted;
            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Encoding.Default.GetBytes(Key);
                aesAlg.IV = Encoding.Default.GetBytes(IV);

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        csEncrypt.Write(rawBytes, 0, rawBytes.Length);
                        csEncrypt.FlushFinalBlock();
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }
            // Return the encrypted bytes from the memory stream.
            return encrypted;
        }

        public static byte[] AESEncrypt(byte[] rawBytes, string Key)
        {
            return AESEncrypt(rawBytes, Key, "nightmareisfunny");
        }

        public static byte[] AESDecrypt(byte[] rawBytes, string Key, string IV)
        {
            // Check arguments.
            if (rawBytes == null || rawBytes.Length <= 0)
                throw new ArgumentNullException("RawBytes");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            byte[] decrypted;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Encoding.Default.GetBytes(Key);
                aesAlg.IV = Encoding.Default.GetBytes(IV);
                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream())
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
                    {
                        csDecrypt.Write(rawBytes, 0, rawBytes.Length);
                        csDecrypt.FlushFinalBlock();
                        decrypted = msDecrypt.ToArray();
                    }
                }
            }
            return decrypted;
        }

        public static byte[] AESDecrypt(byte[] rawBytes, string Key)
        {
            return AESDecrypt(rawBytes, Key, "nightmareisfunny");
        }

        public static byte[] AESEncryptNetWork(byte[] toEncryptArray)
        {
            byte[] keyArray = GetMD5Bytes("aichiji..");
            byte[] ivArray = GetMD5Bytes("comeon!!baby");

            RijndaelManaged rDel = new RijndaelManaged();
            rDel.Key = keyArray;
            rDel.IV = ivArray;
            rDel.Mode = CipherMode.CBC;
            rDel.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = rDel.CreateEncryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
            return resultArray;
        }

        public static byte[] AESDecryptNetWork(byte[] DecryptByte)
        {
            byte[] keyArray = GetMD5Bytes("aichiji..");
            byte[] ivArray = GetMD5Bytes("comeon!!baby");

            RijndaelManaged rDel = new RijndaelManaged();
            rDel.Key = keyArray;
            rDel.IV = ivArray;
            rDel.Mode = CipherMode.CBC;
            rDel.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = rDel.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(DecryptByte, 0, DecryptByte.Length);

            return resultArray;
        }


        //File operate code
        public static bool IsFileExist(string filePath)
        {
            return File.Exists(filePath);
        }

        public static bool IsDirectoryExist(string directory)
        {
            return Directory.Exists(directory);
        }

        public static bool CreateDirectory(string directory)
        {
            if (IsDirectoryExist(directory))
            {
                return true;
            }

            int tryCount = 0;
            bool result;
            while (true)
            {
                try
                {
                    Directory.CreateDirectory(directory);
                    result = true;
                    break;
                }
                catch (Exception e)
                {
                    tryCount++;
                    if (tryCount >= 3)
                    {
                        Debugger.LogError("Create Directory " + directory + " Error! Exception = " + e.Message);
                        s_delegateOnOperateFileFail(directory, eFileOperation.E_CreateDirectory);
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }

        public static bool DeleteDirectory(string directory)
        {
            if (!IsDirectoryExist(directory))
            {
                return true;
            }
            int tryCount = 0;
            bool result;
            while (true)
            {
                try
                {
                    Directory.Delete(directory, true);
                    result = true;
                    break;
                }
                catch (Exception e)
                {
                    tryCount++;
                    if (tryCount >= 3)
                    {
                        Debugger.LogError("Delete Directory " + directory + " Error! Exception = " + e.Message);
                        s_delegateOnOperateFileFail(directory, eFileOperation.E_DeleteDirectory);
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }

        public static int GetFileLength(string filePath)
        {
            if (!IsFileExist(filePath))
            {
                return 0;
            }

            int tryCount = 0;
            int result;
            while (true)
            {
                try
                {
                    FileInfo fileInfo = new FileInfo(filePath);
                    result = (int)fileInfo.Length;
                    break;
                }
                catch (Exception e)
                {
                    tryCount++;
                    if (tryCount >= 3)
                    {
                        Debug.LogError("Get FileLength of " + filePath + " Error! Exception = " + e.Message);
                        result = 0;
                        break;
                    }
                }
            }
            return result;
        }

        public static byte[] ReadFile(string filePath)
        {
            if (!IsFileExist(filePath))
            {
                return null;
            }

            byte[] aData = null;
            int tryCount = 0;

            do
            {
                try
                {
                    aData = File.ReadAllBytes(filePath);
                }
                catch (Exception e)
                {
                    Debug.LogError("Get FileLength of " + filePath + " Error! Exception = " + e.Message);
                    aData = null;
                }

                if (aData != null && aData.Length > 0)
                {
                    return aData;
                }
                tryCount++;
            } while (tryCount < 3);

            s_delegateOnOperateFileFail(filePath, eFileOperation.E_ReadFile);

            return null;
        }

        public static bool WriteFile(string filePath, byte[] data)
        {
            int tryCount = 0;
            bool result;
            while (true)
            {
                try
                {
                    File.WriteAllBytes(filePath, data);
                    result = true;
                    break;
                }
                catch (Exception e)
                {
                    tryCount++;
                    if (tryCount >= 3)
                    {
                        Debug.LogError("Write File " + filePath + " Error! Exception = " + e.Message);
                        DeleteFile(filePath);
                        s_delegateOnOperateFileFail(filePath, eFileOperation.E_WriteFile);
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }

        public static bool WriteFile(string filePath, byte[] data, int offset, int length)
        {
            FileStream fileStream = null;
            int tryCount = 0;
            bool result;
            while (true)
            {
                try
                {
                    fileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);   // 4, 2, 3
                    fileStream.Write(data, offset, length);
                    fileStream.Close();
                    result = true;
                    break;
                }
                catch (Exception e)
                {
                    if (fileStream != null)
                    {
                        fileStream.Close();
                    }

                    tryCount++;
                    if (tryCount >= 3)
                    {
                        Debug.LogError("Write File " + filePath + " Error! Exception = " + e.Message);
                        DeleteFile(filePath);
                        s_delegateOnOperateFileFail(filePath, eFileOperation.E_WriteFile);
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }

        public static bool DeleteFile(string filePath)
        {
            if (!IsFileExist(filePath))
            {
                return true;
            }

            int tryCount = 0;
            bool result;
            while (true)
            {
                try
                {
                    File.Delete(filePath);
                    result = true;
                    break;
                }
                catch (Exception e)
                {
                    tryCount++;
                    if (tryCount >= 3)
                    {
                        Debug.LogError("Delete File " + filePath + " Error! Exception = " + e.Message);
                        s_delegateOnOperateFileFail(filePath, eFileOperation.E_DeleteFile);
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }

        public static void CopyFile(string srcFile, string dstFile)
        {
            File.Copy(srcFile, dstFile, true);
        }

        public static string GetFileMd5(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                return string.Empty;
            }
            if (!IsFileExist(filePath))
            {
                return string.Empty;
            }
            return BitConverter.ToString(s_md5Provider.ComputeHash(ReadFile(filePath))).Replace("-", string.Empty);
        }

        public static string GetMD5(byte[] data)
        {
            return BitConverter.ToString(s_md5Provider.ComputeHash(data)).Replace("-", string.Empty);
        }

        public static string CalcMd5Hash(byte[] data)
        {
            byte[] hash = s_md5Provider.ComputeHash(data);
            var sb = new StringBuilder();
            for (int i = 0; i < hash.Length; ++i)
            {
                sb.Append(hash[i].ToString("x2"));
            }
            return sb.ToString();
        }

        public static string GetMD5(string str)
        {
            return BitConverter.ToString(s_md5Provider.ComputeHash(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty);
        }

        public static byte[] GetMD5Bytes(string str)
        {
            byte[] byteHashedPassword;
            using (MD5 md5 = MD5.Create())
            {
                byteHashedPassword = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            }
            return byteHashedPassword;
        }

        public static string CombinePath(string firstPath, string secondPath)
        {
            if (firstPath.LastIndexOf('/') != firstPath.Length - 1)
            {
                firstPath += "/";
            }

            if (secondPath.IndexOf('/') == 0)
            {
                secondPath = secondPath.Substring(1);
            }
            return firstPath + secondPath;
        }

        public static string CombinePaths(params string[] values)
        {
            if (values.Length <= 0)
            {
                return string.Empty;
            }

            if (values.Length == 1)
            {
                return CombinePath(values[0], string.Empty);
            }

            if (values.Length > 1)
            {
                string path = CombinePath(values[0], values[1]);
                for (int i = 2; i < values.Length; i++)
                {
                    path = CombinePath(path, values[i]);
                }
                return path;
            }

            return string.Empty;
        }

        public static string GetStreamingAssetsPathWithHeader(string fileName)
        {
            return Path.Combine(Application.streamingAssetsPath, fileName);
        }

        public static string GetCachePath()
        {
            if (string.IsNullOrEmpty(s_cachePath))
            {
                //DebugHelper.LogError("s_cachePath IS NULL");
                s_cachePath = Application.persistentDataPath;
            }
            return s_cachePath;
        }

        public static string GetCachePath(string fileName)
        {
            return CombinePath(GetCachePath(), fileName);
        }

        public static string GetCachePathWithHeader(string fileName)
        {
            return s_localPathUrlHeader + GetCachePath(fileName);
        }

        public static string GetIFSExtractPath()
        {
            if (string.IsNullOrEmpty(s_ifsExtractPath))
            {
                s_ifsExtractPath = CombinePath(GetCachePath(), s_ifsExtractFolder);
            }
            return s_ifsExtractPath;
        }

        public static string GetFullName(string fullPath)
        {
            if (string.IsNullOrEmpty(fullPath))
            {
                return string.Empty;
            }

            int pos = fullPath.LastIndexOf("/");
            if (pos > 0)
            {
                return fullPath.Substring(pos + 1, fullPath.Length - pos - 1);
            }
            return fullPath;
        }

        public static string EraseExtension(string fullName)
        {
            if (string.IsNullOrEmpty(fullName))
            {
                return string.Empty;
            }

            int pos = fullName.LastIndexOf('.');
            if (pos > 0)
            {
                return fullName.Substring(0, pos);
            }
            return fullName;
        }

        public static string GetExtension(string fullName)
        {
            if (string.IsNullOrEmpty(fullName))
            {
                return string.Empty;
            }
            int pos = fullName.LastIndexOf('.');
            if (pos > 0 && pos + 1 < fullName.Length)
            {
                return fullName.Substring(pos);
            }
            return string.Empty;
        }

        public static string GetFullDirectory(string fullPath)
        {
            if (string.IsNullOrEmpty(fullPath))
            {
                return string.Empty;
            }
            return Path.GetDirectoryName(fullPath);
        }

        public static bool ClearDirectory(string fullPath)
        {
            bool result;
            try
            {
                string[] files = Directory.GetFiles(fullPath);
                for (int i = 0; i < files.Length; i++)
                {
                    File.Delete(files[i]);
                }
                string[] directories = Directory.GetDirectories(fullPath);
                for (int j = 0; j < directories.Length; j++)
                {
                    Directory.Delete(directories[j], true);
                }
                result = true;
            }
            catch
            {
                result = false;
            }
            return result;
        }

        public static bool ClearDirectory(string fullPath, string[] fileExtensionFilter, string[] folderFilter)
        {
            bool result;
            try
            {
                if (fileExtensionFilter != null)
                {
                    string[] files = Directory.GetFiles(fullPath);
                    for (int i = 0; i < files.Length; i++)
                    {
                        if (fileExtensionFilter.Length > 0)
                        {
                            for (int j = 0; j < fileExtensionFilter.Length; j++)
                            {
                                if (files[i].Contains(fileExtensionFilter[j]))
                                {
                                    DeleteFile(files[i]);
                                    break;
                                }
                            }
                        }
                    }
                }

                if (folderFilter != null)
                {
                    string[] directories = Directory.GetDirectories(fullPath);
                    for (int k = 0; k < directories.Length; k++)
                    {
                        if (folderFilter != null && folderFilter.Length > 0)
                        {
                            for (int l = 0; l < folderFilter.Length; l++)
                            {
                                if (directories[k].Contains(folderFilter[l]))
                                {
                                    DeleteDirectory(directories[k]);
                                    break;
                                }
                            }
                        }
                    }
                }
                result = true;
            }
            catch
            {
                result = false;
            }
            return result;
        }
    }
}
